{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercise 5 - Actor Handles\n",
    "\n",
    "**GOAL:** The goal of this exercise is to show how to pass around actor handles.\n",
    "\n",
    "Suppose we wish to have multiple tasks invoke methods on the same actor. For example, we may have a single actor that records logging information from a number of tasks. We can achieve this by passing a handle to the actor as an argument into the relevant tasks.\n",
    "\n",
    "### Concepts for this Exercise - Actor  Handles\n",
    "\n",
    "First of all, suppose we've created an actor as follows.\n",
    "\n",
    "```python\n",
    "@ray.remote\n",
    "class Actor(object):\n",
    "    def method(self):\n",
    "        pass\n",
    "\n",
    "# Create the actor\n",
    "actor = Actor.remote()\n",
    "```\n",
    "\n",
    "Then we can define a remote function (or another actor) that takes an actor handle as an argument.\n",
    "\n",
    "```python\n",
    "@ray.remote\n",
    "def f(actor):\n",
    "    # We can invoke methods on the actor.\n",
    "    x_id = actor.method.remote()\n",
    "    # We can block and get the results.\n",
    "    return ray.get(x_id)\n",
    "```\n",
    "\n",
    "Then we can invoke the remote function a few times and pass in the actor handle.\n",
    "\n",
    "```python\n",
    "# Each of the three tasks created below will invoke methods on the same actor.\n",
    "f.remote(actor)\n",
    "f.remote(actor)\n",
    "f.remote(actor)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "from collections import defaultdict\n",
    "import ray\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ray.init(num_cpus=4, include_webui=False, ignore_reinit_error=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this exercise, we're going to write some code that runs several \"experiments\" in parallel and has each experiment log its results to an actor. The driver script can then periodically pull the results from the logging actor.\n",
    "\n",
    "**EXERCISE:** Turn this `LoggingActor` class into an actor class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class LoggingActor(object):\n",
    "    def __init__(self):\n",
    "        self.logs = defaultdict(lambda: [])\n",
    "    \n",
    "    def log(self, index, message):\n",
    "        self.logs[index].append(message)\n",
    "    \n",
    "    def get_logs(self):\n",
    "        return dict(self.logs)\n",
    "\n",
    "\n",
    "assert hasattr(LoggingActor, 'remote'), ('You need to turn LoggingActor into an '\n",
    "                                         'actor (by using the ray.remote keyword).')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**EXERCISE:** Instantiate the actor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logging_actor = LoggingActor()\n",
    "\n",
    "# Some checks to make sure this was done correctly.\n",
    "assert hasattr(logging_actor, 'get_logs')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we define a remote function that runs and pushes its logs to the `LoggingActor`.\n",
    "\n",
    "**EXERCISE:** Modify this function so that it invokes methods correctly on `logging_actor` (you need to change the way you call the `log` method)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@ray.remote\n",
    "def run_experiment(experiment_index, logging_actor):\n",
    "    for i in range(60):\n",
    "        time.sleep(1)\n",
    "        # Push a logging message to the actor.\n",
    "        logging_actor.log(experiment_index, 'On iteration {}'.format(i))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we create several tasks that use the logging actor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "experiment_ids = [run_experiment.remote(i, logging_actor) for i in range(3)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While the experiments are running in the background, the driver process (that is, this Jupyter notebook) can query the actor to read the logs.\n",
    "\n",
    "**EXERCISE:** Modify the code below to dispatch methods to the `LoggingActor`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logs = logging_actor.get_logs()\n",
    "\n",
    "assert isinstance(logs, dict), (\"Make sure that you dispatch tasks to the \"\n",
    "                                \"actor using the .remote keyword and get the results using ray.get.\")\n",
    "logs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**EXERCISE:** Try running the above box multiple times and see how the results change (while the experiments are still running in the background). You can also try running more of the experiment tasks and see what happens."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
